.intel_syntax noprefix

#include "config.h"
#include "x86.h"

# GDT 32 segment selectors
.set GDT32_CSEG_SEL, 8
.set GDT32_DSEG_SEL, 16

# Protected mode enable flag
.set CR0_PE_ON, 1

#
# Second-stage bootloader
#
#ifndef BOOT_NUM_SECTORS
#error "BOOT_NUM_SECTORS undefined"
#endif
.set BOOT_ADDRESS, 0x8000
.set BOOT_ADDRESS_16SEG, 0
.set BOOT_ADDRESS_16OFF, 0x8000
.set BOOT_START_SECTOR, __RPI_OS_PARTITION_START_SECTOR__ + 1

#
# Real-mode thunk
#
#ifndef REALTHUNK_NUM_SECTORS
#error "REALTHUNK_NUM_SECTORS undefined"
#endif
.set REALTHUNK_ADDRESS, 0x8400
.set REALTHUNK_ADDRESS_16SEG, 0
.set REALTHUNK_ADDRESS_16OFF, 0x8400
.set REALTHUNK_START_SECTOR, BOOT_START_SECTOR + BOOT_NUM_SECTORS

#
# Kernel
#
#ifndef KERNEL_NUM_SECTORS
#error "KERNEL_NUM_SECTORS undefined"
#endif
.set KERNEL_ADDRESS, 0x100000
.set KERNEL_ADDRESS_16SEG, 0xFFFF
.set KERNEL_ADDRESS_16OFF, 0x10
.set KERNEL_START_SECTOR, REALTHUNK_START_SECTOR + REALTHUNK_NUM_SECTORS

#
# MBRBOOT:
#   Enables A20
#   Maps memory
#   Loads boot, and kernel
#   Transition to 32 bit protected mode
#   Call boot
#
.globl MBRBoot
MBRBoot:
  .code16
  cli
  cld

  xor ax, ax
  mov ss, ax
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax

  #
  # Enable A20:
  #   For backwards compatibility with the earliest PCs, physical
  #   address line 20 is tied low, so that addresses higher than
  #   1MB wrap around to zero by default.  This code undoes this.
  #
SetA20.1:
  in al, 0x64   # Wait for not busy
  test al, 0x2
  jnz SetA20.1

  mov al, 0xd1  # 0xd1 -> port 0x64
  out 0x64, al 
SetA20.2:
  in al, 0x64   # Wait for not busy
  test al, 0x2
  jnz SetA20.2

  mov al, 0xdf  # 0xdf -> port 0x60
  outb 0x60, al

  #
  # Detect memory
  #   INT 15h, EAX=E820h
  #   Destination = ES:DI (0xD0:0 = 0xD00)
  #
  mov ax, 0xD0
  mov es, ax

  xor ebx, ebx
  xor edi, edi
  mov edx, 0x534D4150

  mov eax, 0x1FC0
  mov dword ptr [eax], 0

QuerySystemAddressMap:
  mov ecx, 24
  mov eax, 0xE820
  int 0x15

  mov esi, 0x1FC0
  inc dword ptr [esi]

  # Error handling
  jc QuerySystemAddressMapSuccess
  mov edx, 0x0534D4150 # Some BIOSes trash register

  cmp eax, edx         # On success, eax must have been reset to "SMAP"
  jne QuerySystemAddressMapFailed

  # Check if end of list
  cmp ebx, 0           # If ebx resets to 0, list is complete
  je QuerySystemAddressMapSuccess
  
  # No error
  add di, 24
  jmp QuerySystemAddressMap

QuerySystemAddressMapFailed:
  mov dx, 0x3F8
  mov al, 0x46
  out dx, al
  jmp QuerySystemAddressMapFailed

QuerySystemAddressMapSuccess:

  #
  # Real Mode read hd
  #
MBRBootReadDisk:
  mov dl, 0x80 # Drive number (0x80 for hard disk)
  mov ah, 0x42 # Function selector (AH = 42h Extended Read)
  mov si, offset boot_dap
  int 0x13

  mov dl, 0x80 # Drive number (0x80 for hard disk)
  mov ah, 0x42 # Function selector (AH = 42h Extended Read)
  mov si, offset realthunk_dap
  int 0x13

  mov dl, 0x80 # Drive number (0x80 for hard disk)
  mov ah, 0x42 # Function selector (AH = 42h Extended Read)
  mov si, offset kernel_dap
  int 0x13

  # Switch from real to protected mode, using a bootstrap x86_32_gdt and segment translation that makes virtual addresses identical to their physical addresses, so that the effective memory map does not change during the switch.
  lgdt x86_32_gdt_desc
  mov eax, cr0
  or eax, CR0_PE_ON
  mov cr0, eax
  
  # Switch processor into 32-bit mode.
  jmp GDT32_CSEG_SEL, MBRBoot.32

  .code32 # Assemble for 32-bit mode
MBRBoot.32:
  # Set up the protected-mode data segment registers
  mov ax, GDT32_DSEG_SEL    # Our data segment selector
  mov ds, ax                # -> DS: Data Segment
  mov es, ax                # -> ES: Extra Segment
  mov fs, ax                # -> FS
  mov gs, ax                # -> GS
  mov ss, ax                # -> SS: Stack Segment
  
  # Initialize stack 
  mov ebp, 0x3FFFFF
  mov esp, ebp

  # Jump to boot
  jmp BOOT_ADDRESS_16OFF

#
# DATA
#

.balign 4
boot_dap:
.byte 0x10               # Size of packet
.byte 0                  # Unused
.byte BOOT_NUM_SECTORS   # Num sectors
.byte 0                  # Unused
.word BOOT_ADDRESS_16OFF # Offset
.word BOOT_ADDRESS_16SEG # Segment
.quad BOOT_START_SECTOR  # Sector start

.balign 4
kernel_dap:
.byte 0x10                 # Size of packet
.byte 0                    # Unused
.byte KERNEL_NUM_SECTORS   # Num sectors
.byte 0                    # Unused
.word KERNEL_ADDRESS_16OFF # Offset
.word KERNEL_ADDRESS_16SEG # Segment
.quad KERNEL_START_SECTOR  # Sector start

.balign 4
realthunk_dap:
.byte 0x10                    # Size of packet
.byte 0                       # Unused
.byte REALTHUNK_NUM_SECTORS   # Num sectors
.byte 0                       # Unused
.word REALTHUNK_ADDRESS_16OFF # Offset
.word REALTHUNK_ADDRESS_16SEG # Segment
.quad REALTHUNK_START_SECTOR  # Sector start

# Identity GDT
.balign 8
x86_32_gdt:
  SEG_NULL                            # null seg
  SEG32(STA_X|STA_R, 0x0, 0xFFFFFFFF) # code seg
  SEG32(STA_W, 0x0, 0xFFFFFFFF)       # data seg
  SEG16(STA_X|STA_R, 0x0, 0xFFFF)     # thunk code seg
  SEG16(STA_W, 0x0, 0xFFFF)           # thunk data seg

.balign 8
# Identity GDT descriptor
x86_32_gdt_desc:
  .word 8*5 - 1                       # sizeof(x86_32_gdt) - 1
  .long x86_32_gdt                    # address x86_32_gdt
